package com.smsc.headend.task.engine.executor.impl.collection;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dmo.DeviceSAPandKeys;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.enums.DictionaryType;
import com.smsc.headend.module.data.enums.OperationGroup;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.report.entity.LogScheduleTaskDevice;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.module.task.enums.DeviceActionType;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.executor.TaskExecutor;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.handler.impl.collection.ReadEventLogBufferHandler;
import com.smsc.headend.task.engine.handler.impl.collection.ReadLoadProfileBufferHandler;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.task.engine.service.CollMeterTaskPlanService;
import com.smsc.headend.task.engine.service.ConnectorService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.collection.CollectTaskService;
import com.smsc.headend.task.engine.service.collection.RecollectTaskService;
import com.smsc.headend.task.engine.service.task.LogScheduleTaskDeviceService;
import com.smsc.headend.task.engine.service.task.TaskPacketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @author liangli
 * @date 2020/8/31
 */
@Slf4j
public class ReadDCUCollectionDataExecutor implements TaskExecutor {
    @Autowired
    TaskPacketService packetService;
    @Autowired
    ConnectorService connectorService;

    @Autowired
    RedisUtils redisUtils;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    DataItemFacade dataItemFacade;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    AssetService assetService;
    @Autowired
    CollectTaskService collectTaskService;
    @Autowired
    RecollectTaskService recollectTaskService;
    @Autowired
    CollMeterTaskPlanService collMeterTaskPlanService;
    @Autowired
    LogScheduleTaskDeviceService logScheduleTaskDeviceService;

    @Override
    public AtomicTaskState execute(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        readProfilePreCheck(task, atomicTask);
        Boolean isRecollect = TaskType.AutoReCollectProfile.toString().equalsIgnoreCase(task.getTaskType());
        if (isRecollect) {
            log.info("readyEmitAutoRecollectTask,taskNo:{},param:{}", task.getTaskNo(), task.getParamJson());
        }
        DeviceOperation dictionary = assetService.getDictionary(atomicTask.getOperationId(), DictionaryType.Operation.getType());
        Meter meter = getMeter(task, assetService);
        if (task.getMeterId() != null && meter == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.METER_NOT_EXISTS.toString());
        }
        ComEntity terminal = assetService.getComById(task.getComId());
        ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());
        if (terminal == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.TERMINAL_NOT_EXISTS.toString());
        }
        Map<String, Object> paramMap = getParameterMap(task, atomicTask);
        List<DataItemDTO> dataItemDTOList = getDCUReadCollectionData(meter, terminal, paramMap, dictionary, comConfigDMO);
        Long profileDataItemId = Long.valueOf(String.valueOf(paramMap.get("dataItemId") + "102")); // 102是7类buffer属性
        //曲线cosem
        List<DataItemDTO> profileDataItemDTO = dataItemFacade.getDataItemsById(meter.getModelId(), DeviceActionType.Get, profileDataItemId);
        if (CollectionUtils.isEmpty(profileDataItemDTO) || CollectionUtils.isEmpty(dataItemDTOList)) {
            log.error("dataItemList {} , profile Data item {}", dataItemDTOList, profileDataItemDTO);
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.DATAITEM_NOT_EXISTS.toString());
        }
        AtomicTaskState state = taskDAO.hashGetAtomicTaskState(atomicTask.getAtomicTaskNo());

        Map<String, Object> initialedMap = collectTaskService.scheduleTaskTimeInit(task, paramMap, meter, task.getTaskType());

        Date[] profileStartEnd = collectTaskService.getProfileStartEndDate(task, atomicTask, initialedMap, state);
        Date startDate = profileStartEnd[0];
        Date endDate = profileStartEnd[1];
        if (startDate.after(endDate)) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.PARAM_ERROR.getErrorCode());
        }
        DeviceSAPandKeys deviceKeys = assetManagementRemoteService.getTerminalCommunicationKey(terminal.getComId(), dataItemDTOList.get(0).getRoleId());
        byte[] packets = packetService.buildDCUScheduleRead(terminal, meter, dataItemDTOList.get(0), profileDataItemDTO.get(0), startDate, endDate, comConfigDMO, deviceKeys);
        task.setMeterId(null);//read from dcu need to set Connector Task meter Id to Null

        if (state == null) {
            state = createAtomicState((atomicTask.getGroupId() == OperationGroup.ScheduleReadProfiles.getId().intValue()
                    || atomicTask.getGroupId() == OperationGroup.ODRReadProfiles.getId().intValue()) ? ReadLoadProfileBufferHandler.class : ReadEventLogBufferHandler.class);
        }
        state.setCollectedEndTv(startDate.getTime() / 1000);
        state.setCollectingEndTv(endDate.getTime() / 1000);
        taskDAO.hashSetAtomicTaskState(atomicTask.getAtomicTaskNo(), task, state.getRetryTimes(), state);
        state.setStored(true);
        if (StrUtil.equalsAny(task.getTaskType(), TaskType.Interface_ODR_Profile.toString(), TaskType.Interface_ODR_LogBook.toString())) {
            // 该task缓存报文
            connectorService.sendPacket(task, atomicTask, dataItemDTOList.get(0).getRoleId(), true, packets);
        } else {
            connectorService.sendPacket(task, atomicTask, dataItemDTOList.get(0).getRoleId(), task.getIsODR(), packets);
        }
        if (isRecollect) {
            log.info("autoRecollectTaskEmitted,taskNo:{},state", task.getTaskNo(), JSONUtil.toJsonStr(state));
        }
        task.setMeterId(meter.getMeterId());//set back after send
        return state;
    }

    private boolean readProfilePreCheck(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        if (TaskType.AutoReCollectProfile.toString().equalsIgnoreCase(task.getTaskType())) {
            recollectTaskService.changeDetailIfRequired(task, atomicTask);
        }
        return true;
    }


    @Override
    public Boolean handleFailure(Task task, AtomicTask atomicTask, String message) {
        String key = "message";
        if (atomicTask.getGroupId() == OperationGroup.ODRReadProfiles.getId().intValue() || atomicTask.getGroupId() == OperationGroup.ODRReadLogBook.getId().intValue()) {
            Map<String, Object> m = JSONUtil.toBean(task.getParamJson(), Map.class);
            key = m.get("seq") != null ? Convert.toStr(m.get("seq")) : key;
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), key, message, 3600l, false);
        }
        if (TaskType.AutoReCollectProfile.toString().equalsIgnoreCase(task.getTaskType())) {
            log.info("recollect: auto recollect failed, {}", message);
        }
        if (TaskType.ScheduleRead_Profile.toString().equalsIgnoreCase(task.getTaskType()) && !message.equals(TaskExecuteErrorCode.SCHEDULE_TASK_IS_LATEST_PROCESS.toString())
                || TaskType.EventRead_Profile.toString().equalsIgnoreCase(task.getTaskType())) {
            if (UDISTaskExecutorException.COM_STATUS_EXCEPTION.equalsIgnoreCase(message)) {
                log.warn("readExecutorPauseAsComOffline");
                return false;
            }
            logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Failed.getCode(), LogScheduleTaskDevice.StatusDescEnum.DecodeDataFailed.getCode());
        }
        return true;
    }

    private List<DataItemDTO> getDCUReadCollectionData(Meter meter, ComEntity terminal, Map<String, Object> paramMap, DeviceOperation dictionary, ComConfigDMO comConfigDMO) {
        if (paramMap.containsKey("dcuDataItemId")) {
            return dataItemFacade.getDataItemsById(comConfigDMO.getModelId(), DeviceActionType.Get, Convert.toLong(paramMap.get("dcuDataItemId")));
        }
        return dataItemFacade.getDataItemsByOperation(meter, terminal, dictionary, paramMap, comConfigDMO.getModelId());
    }
}
